package ui.tp3.modelo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Random;

public class Tablero implements Serializable {
	private static final long serialVersionUID = 1L;

	/******************** VARIABLES ****************/

	private static final Tablero instance = new Tablero();

	private Matriz matriz;
	private ArrayList<Esfera> esferas = new ArrayList<Esfera>();
	private ArrayList<Personaje> personajes = new ArrayList<Personaje>();
	private ArrayList<String> personajesDelJuego;
	private ArrayList<Posicionable> elementos = new ArrayList<Posicionable>();

	/************** GETTERS Y SETTERS *******************/

	public ArrayList<Posicionable> getElementos() {
		return elementos;
	}

	public Matriz getMatriz() {
		return matriz;
	}

	public void setMatriz(Matriz matriz) {
		this.matriz = matriz;
	}

	public void setElementos(ArrayList<Posicionable> es) {
		this.elementos = es;
	}

	public ArrayList<Esfera> getEsferas() {
		return esferas;
	}

	public void setEsferas(ArrayList<Esfera> esferas) {
		this.esferas = esferas;
	}

	public ArrayList<Personaje> getPersonajes() {
		return personajes;
	}

	public void setPersonajes(ArrayList<Personaje> personajes) {
		this.personajes = personajes;

	}

	public void setPersonajesDelJuego(ArrayList<String> personajesDelJuego) {
		this.personajesDelJuego = personajesDelJuego;
	}

	public ArrayList<String> getPersonajesDelJuego() {
		return personajesDelJuego;
	}

	public static Tablero getInstance() {
		return instance;
	}

	/**************** CONSTRUCTOR ************************/

	public Tablero(int limiteX, int limiteY) throws UserException {

		this.crearPersonajesDelJuego();

		this.matriz = new Matriz(limiteX, limiteY);

	}

	public Tablero() {
		this.crearPersonajesDelJuego();
	}
	
	/**************** METODOS *******************/

	/**
	 * Este metodo genera personajes y esferas por defecto para iniciar el
	 * tablero
	 */
	public void generarPersonajesYEsferasPorDefecto() {
		try {
			Coordenada c = new Coordenada(0, 0);
			Personaje p = new Personaje("Goku", c, 15, this);
			this.agregarPersonaje(p);
			Coordenada c1 = new Coordenada(1, 0);
			Personaje p1 = new Personaje("Trunks", c1, 1, this);
			this.agregarPersonaje(p1);
			Coordenada c2 = new Coordenada(0, 1);
			Esfera e1 = new Esfera(c2, 5);
			Coordenada c3 = new Coordenada(2, 0);
			Esfera e2 = new Esfera(c3, 6);
			Coordenada c4 = new Coordenada(3, 2);
			Esfera e3 = new Esfera(c4, 7);
			Coordenada c5 = new Coordenada(0, 4);
			Esfera e4 = new Esfera(c5, 4);
			Coordenada c6 = new Coordenada(4, 2);
			Esfera e5 = new Esfera(c6, 3);
			this.agregarEsfera(e1);
			this.agregarEsfera(e2);
			this.agregarEsfera(e3);
			this.agregarEsfera(e4);
			this.agregarEsfera(e5);
		} catch (UserException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Este metodo crea la lista con los nombres de los personajes disponibles
	 * en el juego.
	 */
	protected void crearPersonajesDelJuego() {
		this.personajesDelJuego = new ArrayList<String>();
		this.personajesDelJuego.add("Goku");
		this.personajesDelJuego.add("Vegeta");
		this.personajesDelJuego.add("Gohan");
		this.personajesDelJuego.add("Trunks");
		this.personajesDelJuego.add("Bulma");
		this.personajesDelJuego.add("Videl");

	}

	/**
	 * Revisa si en en la coordenada c pasada por parametro hay algun personaje
	 * y retorna un booleano, true si lo hay y false si no hay.
	 * 
	 * @param Coordenada
	 *            c
	 * @return boolean
	 */
	boolean hayPersonajes(Coordenada c) {
		boolean respuesta = false;
		for (Personaje p : this.personajes) {
			respuesta = respuesta || (p.estaEnLaCoordenada(c));
		}
		return respuesta;
	}

	/**
	 * Revisa si en en la coordenada coord pasada por parametro esta fuera o
	 * dentro del limite y retorna un booleano, true si lo esta y false si no.
	 * 
	 * @param Coordenada
	 *            coord
	 * @return boolean
	 */
	boolean enLimite(Coordenada coord) {

		return this.matriz.enLimite(coord);

	}

	/**
	 * Revisa si en en la coordenada x pasada por parametro hay algun
	 * "obstaculo" y retorna un booleano, true si lo hay y false si no hay.
	 * 
	 * @param Coordenada
	 *            x
	 * @return boolean
	 */
	public boolean hayLugar(Coordenada a) {
		return !hayEsferas(a) && enLimite(a) && !hayPersonajes(a);
	}

	/**
	 * Revisa si en en la coordenada x pasada por parametro hay una esfera y
	 * retorna un booleano, true si lo hay y false si no hay.
	 * 
	 * @param Coordenada
	 *            x
	 * @return boolean
	 */
	boolean hayEsferas(Coordenada x) {

		boolean respuesta = false;
		for (Esfera e : this.esferas) {
			Coordenada coord = e.getCoordenada();
			respuesta = respuesta || coord.equals(x);
		}

		return respuesta;
	}

	/**
	 * Este metodo retorna un booleano representando si la esfera pasada por
	 * parametro ya existe, teniendo en cuenta que no puede haber dos esferas
	 * con la misma cantidad de estrellas.
	 * 
	 * @param e
	 * @return boolean
	 */
	boolean existeEsferas(Esfera e) {

		boolean respuesta = false;
		for (Esfera es : this.esferas) {
			respuesta = respuesta
					|| es.getNumeroEstrellas() == (e.getNumeroEstrellas());
		}

		return respuesta;
	}

	/**
	 * Este metodo retorna el personaje que esta en la coordenada c, si es que
	 * hay alguno, de lo contrario, devuelve null.
	 * 
	 * @param Coordenada
	 *            c
	 * @return Personaje
	 */
	Personaje enCoordenada(Coordenada c) {

		Personaje per = null;
		int i = 0;
		for (Personaje p : this.personajes) {
			if (p.estaEnLaCoordenada(c)) {
				per = p;
				return per;
			}
			i++;
		}
		i = 0;
		return per;
	}

	/**
	 * Este metodo se encarga de ubicar cualquier posicionable en la matriz y en
	 * la lista de elementos del tablero.
	 * 
	 * @param elemento
	 * @param c
	 */
	protected void ubicarElementoEnCoord(Posicionable elemento, Coordenada c) {

		this.matriz.ubicarElemento(elemento, c);
		this.elementos.add(elemento);
	}

	/**
	 * Este metodo se encarga de verificar si se puede o no agregar un personaje
	 * y si es posible, de hacerlo.
	 * 
	 * @param personaje
	 * @throws UserException
	 *             si ya existe o si no hay lugar.
	 */
	public void agregarPersonaje(Personaje p) throws UserException {
		if (this.hayLugar(p.getCoordenada())) {

			this.personajes.add(p);
			this.ubicarElementoEnCoord(p, p.getCoordenada());
		} else {
			if (this.getPersonajes().contains(p)) {
				throw new UserException("Ese personaje ya existe");
			}
			throw new UserException("Esa posición ya está ocupada");

		}

	}

	/**
	 * Este metodo se encarga de verificar si se puede o no agregar una esfera y
	 * si es posible, de hacerlo.
	 * 
	 * @param esfera
	 * @throws UserException
	 *             si ya existe o si no hay lugar.
	 */
	public void agregarEsfera(Esfera e) throws UserException {
		if (this.hayLugar(e.getCoordenada())) {
			this.esferas.add(e);
			this.ubicarElementoEnCoord(e, e.getCoordenada());
		} else {
			if (this.existeEsferas(e)) {
				throw new UserException("Esa esfera ya existe");
			}
			throw new UserException("Esa posición ya está ocupada");

		}
	}

	/**
	 * Este metodo se encarga de quitar la esfera del tablero
	 * 
	 * @param e
	 */
	public void quitarEsfera(Posicionable e) {
		this.getEsferas().remove(e);
		ArrayList<Posicionable> elemAct = this.getElementos();
		elemAct.remove(e);
		this.matriz.quitarElemento(e);

	}

	/**
	 * Este metodo se encarga de quitar el personaje pasado por parametro del
	 * tablero
	 * 
	 * @param e
	 */
	public void quitarPersonaje(Posicionable p) {
		this.getPersonajes().remove(p);
		ArrayList<Posicionable> elemAct = this.getElementos();
		elemAct.remove(p);
		this.matriz.quitarElemento(p);

	}

	/**
	 * Este metodo lo que hace es renderizar las esferas en un lugar del tablero
	 * al azar.
	 * 
	 * @param ran
	 * @return
	 */
	public void renderEsferas() {
		Random ran = new Random();
		Coordenada c;
		int i = 0;
		while (i < 7) {
			c = this.matriz.coordenadaAleatoria(ran);

			if (this.hayLugar(c)) {
				i++;
				Esfera e = new Esfera(c, i);
				try {
					this.agregarEsfera(e);
				} catch (UserException e1) {
					e1.printStackTrace();
				}
			}
		}

	}
	/**
	 * 	Este metodo nos devuelve una lista con las esferas disponibles para crear
	 * @return
	 */
	public ArrayList<Integer> estrellasDisponibles() {
		ArrayList<Integer> estrellasDisponibles = new ArrayList<Integer>();
		for (int i = 0; i < 7; i++) {
			estrellasDisponibles.add(i + 1);
		}
		return estrellasDisponibles;
	}

	/**
	 * Retorna una lista con los personajes que no han sido usados de los que se
	 * encuentran en el juego
	 * @return
	 */
	public ArrayList<String> personajesDisponibles() {
		ArrayList<String> personajesDisponibles = new ArrayList<String>();
		personajesDisponibles.addAll(this.personajesDelJuego);

		return personajesDisponibles;
	}
}
